#include "ModelRenderer.h"
#include "VertexTypes.h"
#include "resource.h"

DWORD ModelRenderer::maxNumOfInstances = 100;

ModelRenderer::ModelRenderer(void)
{
	contentModule = NULL;
	pDevice = NULL;
	pEffect = NULL;
	pVertexLayout = NULL;
	pTechnique = NULL;
	pDiffuseVariable = NULL;
	pNormalVariable = NULL;
	pSpecularVariable = NULL;
}

ModelRenderer::~ModelRenderer(void)
{
	UnloadGraphicsContent();
}

void ModelRenderer::UnloadGraphicsContent()
{
	pSpecularVariable = NULL;
	pNormalVariable = NULL;
	pDiffuseVariable = NULL;
	pTechnique = NULL;
	if (pVertexLayout != NULL) pVertexLayout->Release();
	pVertexLayout = NULL;
	if (pEffect != NULL) pEffect->Release();
	pEffect = NULL;
	if (contentModule != NULL) FreeLibrary(contentModule);
	contentModule = NULL;
	pDevice = NULL;
}

HRESULT ModelRenderer::LoadGraphicsContent(EnginePointer<ID3D10Device>& pDevice, EnginePointer<ID3D10EffectPool>& pCommonPool)
{
	HRESULT hr;

	this->pDevice = pDevice;

	contentModule = LoadLibrary(L"D3D10EngineContent.dll");
	if (contentModule == NULL) return E_FAIL;

#ifdef _DEBUG
	hr = D3DX10CreateEffectFromResource(contentModule, MAKEINTRESOURCE(OBJECT_RENDER_EFFECT), NULL, NULL, NULL, "fx_4_0", D3D10_SHADER_ENABLE_STRICTNESS | D3D10_SHADER_DEBUG, D3D10_EFFECT_COMPILE_CHILD_EFFECT, pDevice, pCommonPool, NULL,& pEffect, NULL, NULL);
#else
	hr = D3DX10CreateEffectFromResource(contentModule, MAKEINTRESOURCE(OBJECT_RENDER_EFFECT), NULL, NULL, NULL, "fx_4_0", D3D10_SHADER_ENABLE_STRICTNESS, D3D10_EFFECT_COMPILE_CHILD_EFFECT, pDevice, pCommonPool, NULL,& pEffect, NULL, NULL);
#endif

	pTechnique = pEffect->GetTechniqueByName("Render");

	D3D10_PASS_DESC PassDesc;
	pTechnique->GetPassByIndex(0)->GetDesc(&PassDesc);
	hr = pDevice->CreateInputLayout(ModelVertexLayout, ModelVertexNumElements, PassDesc.pIAInputSignature, PassDesc.IAInputSignatureSize,& pVertexLayout);
	if (FAILED(hr)) return hr;

	pDevice->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
	pDevice->IASetInputLayout(pVertexLayout);

	pDiffuseVariable = pEffect->GetVariableByName("Diffuse")->AsShaderResource();
	pNormalVariable = pEffect->GetVariableByName("Normal")->AsShaderResource();
	pSpecularVariable = pEffect->GetVariableByName("Specular")->AsShaderResource();

	D3D10_BUFFER_DESC bd;
	bd.ByteWidth = maxNumOfInstances * sizeof(D3DXMATRIX);
	bd.Usage = D3D10_USAGE_DYNAMIC;
	bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
	bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
	bd.MiscFlags = 0;

	pDevice->CreateBuffer(&bd, NULL, &pInstanceBuffer);

	return S_OK;
}

HRESULT ModelRenderer::Render(EnginePointer<Model>& model, list<EnginePointer<RenderComponent>> components)
{
	HRESULT hr;

	D3D10_TECHNIQUE_DESC techDesc;
	hr = pTechnique->GetDesc(&techDesc);
	if (FAILED(hr)) return hr;

	pDevice->IASetInputLayout(pVertexLayout);

	for (vector<EnginePointer<ModelMesh>>::iterator it = model->meshesBegin(); it != model->meshesEnd(); it++)
	{
		BYTE baseVBCount = (*it)->getNumVertexBuffers();

		ID3D10Buffer** pVB = new ID3D10Buffer*[baseVBCount + 1];
		UINT* strides = new UINT[baseVBCount + 1];
		UINT* offsets = new UINT[baseVBCount + 1];
		memcpy(pVB, (*it)->getVertexBuffers(), sizeof(ID3D10Buffer*) * baseVBCount);
		memcpy(strides, (*it)->getStrides(), sizeof(UINT) * baseVBCount);
		pVB[baseVBCount] = pInstanceBuffer;
		strides[baseVBCount] = sizeof(D3DXMATRIX);
		ZeroMemory(offsets, sizeof(UINT) * (baseVBCount + 1));

		pDevice->IASetIndexBuffer((*it)->getIndexBuffer(), DXGI_FORMAT_R16_UINT, 0);
		for (list<EnginePointer<MeshSubset>>::iterator it2 = (*it)->subsetBegin(); it2 != (*it)->subsetEnd(); it2++)
		{
			hr = pDiffuseVariable->SetResource((*it2)->getMaterial()->getDiffuseView());
			if (FAILED(hr)) return hr;
			hr = pNormalVariable->SetResource((*it2)->getMaterial()->getNormalView());
			if (FAILED(hr)) return hr;
			hr = pSpecularVariable->SetResource((*it2)->getMaterial()->getSpecularView());
			if (FAILED(hr)) return hr;

			D3DXMATRIX* worlds = new D3DXMATRIX[maxNumOfInstances];
			// DWORD is unsigned so there is no -1.
			DWORD i = ULONG_MAX;

			for (list<EnginePointer<RenderComponent>>::iterator it3 = components.begin(); it3 != components.end(); it3++)
			{
				i++;
				worlds[i] = (*it3)->getWorld();

				if (i == maxNumOfInstances - 1)
				{
					D3DXMATRIX* instanceData;
					pInstanceBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, (VOID**)&instanceData);
					memcpy(instanceData, worlds, maxNumOfInstances * sizeof(D3DXMATRIX));
					pInstanceBuffer->Unmap();

					pDevice->IASetVertexBuffers(0, baseVBCount + 1, pVB, strides, offsets);

					for (UINT p = 0; p < techDesc.Passes; ++p)
					{
						pTechnique->GetPassByIndex(p)->Apply(0);
						pDevice->DrawIndexedInstanced((UINT)(*it2)->getIndexCount(), maxNumOfInstances, (UINT)(*it2)->getIndexStart(), (UINT)(*it2)->getVertexStart(), 0);
					}

					i = 0;
					i--;
				}
			}

			if (i != ULONG_MAX)
			{
				D3DXMATRIX* instanceData;
				pInstanceBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, (VOID**)&instanceData);
				memcpy(instanceData, worlds, (i+1) * sizeof(D3DXMATRIX));
				pInstanceBuffer->Unmap();

				pDevice->IASetVertexBuffers(0, baseVBCount + 1, pVB, strides, offsets);

				for (UINT p = 0; p < techDesc.Passes; ++p)
				{
					pTechnique->GetPassByIndex(p)->Apply(0);
					pDevice->DrawIndexedInstanced((UINT)(*it2)->getIndexCount(), i+1, (UINT)(*it2)->getIndexStart(), (UINT)(*it2)->getVertexStart(), 0);
				}
			}
		}
	}

	return S_OK;
}
